package wangwenjun.phase1.synchronizeddemo.booleanlock;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;

/**
 * @author ChangLiang
 * @date 2020/6/10
 */
@Slf4j
public class BooleanLockV2 implements Lock {

    /**
     * 当前拥有锁的线
     */
    private Thread currentThread;

    /**
     * locked 是一个boolean 开关， false 代表当
     * 前该锁没有被任何线程获得或者已经释放， true 代表该锁已经被某个线程获得，该线程就是
     * currentThread
     */
    private boolean locked = false;

    /**
     * 用来存储哪些线程在获取当前线程时进入了阻塞状态
     * 如果不加入static main线程getBlockedList = 0
     * 加入static 正确获取到了
     */
    private List<Thread> blockedList = new ArrayList<>();

    @Override
    public void lock() throws InterruptedException {
        synchronized (this) {
            while (locked) {
                Thread thread = Thread.currentThread();
                /**
                 * 如果当前锁已经被某个线程获得， 则该线程将加入阻塞队列，并且使当前线程wait
                 * 释放对this monitor 的所有权
                 */
                try {
                    if (!blockedList.contains(Thread.currentThread())) {
                        blockedList.add(Thread.currentThread());
                    }
                    this.wait();
                } catch (InterruptedException e) {
                    blockedList.remove(thread);
                    // 继续抛出
                    throw e;
                }
            }
            /**
             * 如果当前锁没有被其他线程获得， 则该线程将尝试从阻塞队列中删除自己（注意： 如
             * 果当前线程从未进入过阻塞队列，删除方法不会有任何影响；如果当前线程是从wait set 中
             * 被唤醒的，则需要从阻塞队列中将自己删除）
             */
            blockedList.remove(Thread.currentThread());
            this.locked = true;
            this.currentThread = Thread.currentThread();
        }
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (mills <= 0) {
                this.lock();
            }
            long remainingMills = mills;
            long endMills = System.currentTimeMillis() + remainingMills;
            while (locked) {
                /**
                 * 如果remainingMills 小于等于0 ， 则意味着当前线程被其他线程唤醒或者在指定的
                 * wait 时间到了之后还没有获得锁，这种情况下会抛出超时的异常
                 */
                if (remainingMills <= 0) {
                    blockedList.remove(Thread.currentThread());
                    throw new TimeoutException("cannot getOutput the lock during " + mills);
                }
                if (!blockedList.contains(Thread.currentThread())) {
                    blockedList.add(Thread.currentThread());
                }
                /**
                 * 等待remainingMills 的毫秒数， 该值最开始是由其他线程传入的，但在多次wait 的
                 * 过程中会重新计算
                 */
                try {
                    this.wait(remainingMills);
                } catch (InterruptedException e) {
                    blockedList.remove(Thread.currentThread());
                    throw e;
                }
                /**
                 * 重新计算remainingMills 时间。
                 */
                remainingMills = endMills - System.currentTimeMillis();
            }
            blockedList.remove(Thread.currentThread());
            this.locked = true;
            this.currentThread = Thread.currentThread();
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            /**
             * 判断当前线程是否为获取锁的那个线程，只有加了锁的线程才有资格进行解锁
             */
            if (currentThread == Thread.currentThread()) {
                this.locked = false;
                log.info(Thread.currentThread() + " release the lock.");
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }
}
